/*
Copyright 2008-2010 Gephi
Authors : Mathieu Bastian <mathieu.bastian@gephi.org>
Website : http://www.gephi.org

This file is part of Gephi.

Gephi is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.

Gephi is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License
along with Gephi.  If not, see <http://www.gnu.org/licenses/>.
*/
package org.gephi.visualization.opengl.octree;

import com.sun.opengl.util.GLUT;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.media.opengl.GL;
import javax.media.opengl.glu.GLU;
import org.gephi.utils.collection.avl.AVLItemAccessor;
import org.gephi.utils.collection.avl.ParamAVLTree;
import org.gephi.utils.collection.avl.AVLItem;
import org.gephi.visualization.apiimpl.ModelImpl;

/**
 *
 * @author Mathieu Bastian
 */
public class Octant implements AVLItem {

    //Static
    private static int OctantIDs = 0;
    //Octree
    private Octree octree;
    //Coordinates
    private float size;
    private float posX;
    private float posY;
    private float posZ;
    private int depth;
    //Attributes
    private final int octantID;
    private int objectsCount = 0;
    private Octant[] children;
    private AtomicBoolean updateFlag = new AtomicBoolean();
    //Models
    private List<ParamAVLTree<ModelImpl>> modelClasses;

    public Octant(Octree octree, int depth, float posX, float posY, float posZ, float size) {
        this.size = size;
        this.posX = posX;
        this.posY = posY;
        this.posZ = posZ;
        this.depth = depth;
        this.octree = octree;
        this.octantID = OctantIDs++;
    }

    public void addObject(int classID, ModelImpl obj) {
        if (children == null && depth < octree.getMaxDepth()) {
            //Create children
            subdivide();
        }

        if (depth == octree.getMaxDepth()) {
            //First item add - Initialize
            if (objectsCount == 0) {
                octree.addLeaf(this);

                modelClasses = new ArrayList<ParamAVLTree<ModelImpl>>(octree.getClassesCount());
                for (int i = 0; i < octree.getClassesCount(); i++) {
                    modelClasses.add(new ParamAVLTree<ModelImpl>(new AVLItemAccessor<ModelImpl>() {

                        public int getNumber(ModelImpl item) {
                            return item.getNumber();
                        }
                    }));
                }
            }

            //Get the list
            ParamAVLTree<ModelImpl> objectClass = this.modelClasses.get(classID);

            //Set Octant
            obj.setOctant(this);

            //Add at this node
            obj.setID(octree.getNextObjectID());
            if (objectClass.add(obj)) {
                objectsCount++;
            }
        } else {
            if (classID == 0 && this == octree.root) {
                //Clamp Hack to avoid nodes to be outside octree
                float quantum = size / 2;
                float x = obj.getObj().x();
                float y = obj.getObj().y();
                float z = obj.getObj().z();
                if (x > posX + quantum) {
                    obj.getObj().setX(posX + quantum);
                } else if (x < posX - quantum) {
                    obj.getObj().setX(posX - quantum);
                }
                if (y > posY + quantum) {
                    obj.getObj().setY(posY + quantum);
                } else if (y < posY - quantum) {
                    obj.getObj().setY(posY - quantum);
                }
                if (z > posZ + quantum) {
                    obj.getObj().setZ(posZ + quantum);
                } else if (z < posZ - quantum) {
                    obj.getObj().setZ(posZ - quantum);
                }
            }

            for (int index : obj.octreePosition(posX, posY, posZ, size)) {
                children[index].addObject(classID, obj);
            }
        }
    }

    public void removeObject(int classID, ModelImpl obj) {
        //Get the list
        ParamAVLTree<ModelImpl> objectClass = this.modelClasses.get(classID);

        if (objectClass.remove(obj)) {
            objectsCount--;
        }

        if (objectsCount == 0) {
            //Remove leaf
            octree.removeLeaf(this);
        }

    }

    public void clear(int classID) {
        ParamAVLTree<ModelImpl> tree = getTree(classID);
        int count = tree.getCount();
        tree.clear();
        objectsCount -= count;
        if (objectsCount == 0) {
            //Remove leaf
            octree.removeLeaf(this);
        }
    }

    public void subdivide() {
        float quantum = size / 4;
        float newSize = size / 2;
        Octant o1 = new Octant(octree, depth + 1, posX + quantum, posY + quantum, posZ - quantum, newSize);
        Octant o2 = new Octant(octree, depth + 1, posX - quantum, posY + quantum, posZ - quantum, newSize);
        Octant o3 = new Octant(octree, depth + 1, posX + quantum, posY + quantum, posZ + quantum, newSize);
        Octant o4 = new Octant(octree, depth + 1, posX - quantum, posY + quantum, posZ + quantum, newSize);

        Octant o5 = new Octant(octree, depth + 1, posX + quantum, posY - quantum, posZ - quantum, newSize);
        Octant o6 = new Octant(octree, depth + 1, posX - quantum, posY - quantum, posZ - quantum, newSize);
        Octant o7 = new Octant(octree, depth + 1, posX + quantum, posY - quantum, posZ + quantum, newSize);
        Octant o8 = new Octant(octree, depth + 1, posX - quantum, posY - quantum, posZ + quantum, newSize);

        children = new Octant[]{o1, o2, o3, o4, o5, o6, o7, o8};
    }

    public Iterator<ModelImpl> iterator(int classID) {
        return this.modelClasses.get(classID).iterator();
    }

    public ParamAVLTree<ModelImpl> getTree(int classID) {
        return modelClasses.get(classID);
    }

    public void displayOctant(GL gl) {
        /*if(children==null && depth==octree.getMaxDepth() && objectsCount>0)
        {*/

        float quantum = size / 2;
        gl.glBegin(GL.GL_QUAD_STRIP);
        gl.glVertex3f(posX + quantum, posY + quantum, posZ + quantum);
        gl.glVertex3f(posX + quantum, posY - quantum, posZ + quantum);
        gl.glVertex3f(posX + quantum, posY + quantum, posZ - quantum);
        gl.glVertex3f(posX + quantum, posY - quantum, posZ - quantum);
        gl.glVertex3f(posX - quantum, posY + quantum, posZ - quantum);
        gl.glVertex3f(posX - quantum, posY - quantum, posZ - quantum);
        gl.glVertex3f(posX - quantum, posY + quantum, posZ + quantum);
        gl.glVertex3f(posX - quantum, posY - quantum, posZ + quantum);
        gl.glVertex3f(posX + quantum, posY + quantum, posZ + quantum);
        gl.glVertex3f(posX + quantum, posY - quantum, posZ + quantum);
        gl.glEnd();
        gl.glBegin(GL.GL_QUADS);
        gl.glVertex3f(posX - quantum, posY + quantum, posZ - quantum);
        gl.glVertex3f(posX - quantum, posY + quantum, posZ + quantum);
        gl.glVertex3f(posX + quantum, posY + quantum, posZ + quantum);
        gl.glVertex3f(posX + quantum, posY + quantum, posZ - quantum);

        gl.glVertex3f(posX - quantum, posY - quantum, posZ + quantum);
        gl.glVertex3f(posX - quantum, posY - quantum, posZ - quantum);
        gl.glVertex3f(posX + quantum, posY - quantum, posZ - quantum);
        gl.glVertex3f(posX + quantum, posY - quantum, posZ + quantum);
        gl.glEnd();
        /*}
        else if(children!=null)
        {
        for(Octant o : children)
        {
        o.displayOctreeNode(gl);
        }
        }*/
    }

    public void displayOctantInfo(GL gl, GLU glu) {
        GLUT glut = new GLUT();

        float quantum = size / 2;
        float height = 15;

        gl.glPushMatrix();
        gl.glTranslatef(posX - quantum, posY + quantum - height, posZ + quantum);
        gl.glScalef(0.1f, 0.1f, 0.1f);
        gl.glColor4f(0.0f, 0.0f, 1.0f, 1.0f);
        glut.glutStrokeString(GLUT.STROKE_MONO_ROMAN, "ID: " + octantID);
        gl.glPopMatrix();

        height += 15;
        gl.glPushMatrix();
        gl.glTranslatef(posX - quantum, posY + quantum - height, posZ + quantum);
        gl.glScalef(0.1f, 0.1f, 0.1f);
        gl.glColor4f(0.0f, 0.0f, 1.0f, 1.0f);
        glut.glutStrokeString(GLUT.STROKE_MONO_ROMAN, "objectsCount: " + objectsCount);
        gl.glPopMatrix();

        int i = 0;
        for (ParamAVLTree<ModelImpl> p : modelClasses) {
            height += 15;
            gl.glPushMatrix();
            gl.glTranslatef(posX - quantum, posY + quantum - height, posZ + quantum);
            gl.glScalef(0.1f, 0.1f, 0.1f);
            gl.glColor4f(0.0f, 0.0f, 1.0f, 1.0f);
            glut.glutStrokeString(GLUT.STROKE_MONO_ROMAN, "class" + (i++) + ": " + p.getCount());
            gl.glPopMatrix();
        }
    }

    public int getNumber() {
        return octantID;
    }

    public float getPosX() {
        return posX;
    }

    public float getPosY() {
        return posY;
    }

    public float getPosZ() {
        return posZ;
    }

    public float getSize() {
        return size;
    }

    public boolean isRequiringUpdatePosition() {
        return updateFlag.get();
    }

    public void requireUpdatePosition() {
        if (!updateFlag.getAndSet(true)) {
            octree.vizController.getScheduler().requireUpdatePosition();
        }
    }

    public void resetUpdatePositionFlag() {
        updateFlag.set(false);
    }
}
